﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Gmantis.Controls.Chart
{
    public interface IChartLayer
    {
        // Properties
        Chart Chart { get; set; }
    }

    public enum MeasureOption
    {
        X,
        Y,
        XY
    }

    [ContentProperty("Axes"), Browsable(false)]
    public class ChartView : FrameworkElement
    {
        // Fields
        private PlotAreaCollection _areas;
        private Axis _ax;
        private AxisCollection _axes = new AxisCollection();
        private Axis _ay;
        private Chart _chart;
        private Dictionary<UIElement, Point> _childPoints = new Dictionary<UIElement, Point>();
        private System.Collections.ObjectModel.ObservableCollection<UIElement> _children;
        private System.Collections.ObjectModel.ObservableCollection<IChartLayer> _layers;
        private Shape _plotShape;
        private IRenderer _renderer;
        private SolidColorBrush _transparentBrush = new SolidColorBrush(Colors.Transparent);
        private ChartViewport2D _vp2;
        public static readonly DependencyProperty InvertedProperty = DependencyProperty.Register("Inverted", typeof(bool), typeof(ChartView), new PropertyMetadata(false, new PropertyChangedCallback(ChartView.OnInvertedChanged)));
        public static readonly DependencyProperty PlotBackgroundProperty = Utils.RegisterProperty("PlotBackground", typeof(Brush), typeof(ChartView), new PropertyChangedCallback(ChartView.OnPlotBackgroundChanged));

        // Methods
        public ChartView()
        {
            this._axes.View = this;
            this.AxisX = new Axis();
            this.AxisY = new Axis();
            this.AxisX.SetFixedType(AxisType.X);
            this.AxisY.SetFixedType(AxisType.Y);
            this._axes.Add(this.AxisX);
            this._axes.Add(this.AxisY);
            this._plotShape = new Rectangle();
            this._plotShape.Fill = new SolidColorBrush(Colors.Transparent);
            this._areas = new PlotAreaCollection();
            this._areas.CollectionChanged += new NotifyCollectionChangedEventHandler(this._areas_CollectionChanged);
            this._layers = new System.Collections.ObjectModel.ObservableCollection<IChartLayer>();
            this._layers.CollectionChanged += new NotifyCollectionChangedEventHandler(this._layers_CollectionChanged);
            this._vp2 = new ChartViewport2D(this);
        }

        private void _areas_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (this.Chart != null)
            {
                this.Chart.InvalidateChart();
            }
        }

        private void _axis_Changed(object sender, EventArgs e)
        {
            ILayout viewElement = this.ViewElement as ILayout;
            if (viewElement != null)
            {
                viewElement.UpdateLayout();
            }
        }

        private void _children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (this._vp2 != null)
            {
                if (e.OldItems != null)
                {
                    foreach (object obj2 in e.OldItems)
                    {
                        UIElement element = obj2 as UIElement;
                        if (this._vp2.Children.Contains(element))
                        {
                            this._vp2.Children.Remove(element);
                        }
                    }
                }
                if (e.NewItems != null)
                {
                    foreach (object obj3 in e.NewItems)
                    {
                        UIElement element2 = obj3 as UIElement;
                        if (!this._vp2.Children.Contains(element2))
                        {
                            this._vp2.Children.Add(element2);
                        }
                    }
                }
            }
        }

        private void _layers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (IChartLayer layer in e.NewItems)
                    {
                        layer.Chart = this._chart;
                    }
                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (IChartLayer layer2 in e.OldItems)
                    {
                        layer2.Chart = null;
                    }
                    break;

                default:
                    if (e.NewItems != null)
                    {
                        foreach (IChartLayer layer3 in e.NewItems)
                        {
                            layer3.Chart = this._chart;
                        }
                    }
                    if (e.OldItems != null)
                    {
                        foreach (IChartLayer layer4 in e.OldItems)
                        {
                            layer4.Chart = null;
                        }
                    }
                    break;
            }
        }

        internal void AddAxis(Axis ax)
        {
            ax.PropertyChanged += new PropertyChangedEventHandler(this._axis_Changed);
        }

        public int DataIndexFromPoint(Point pt, int seriesIndex, MeasureOption option, out double distance)
        {
            distance = double.MaxValue;
            if (seriesIndex >= this._chart.Data.Children.Count)
            {
                return -1;
            }
            DataSeries ds = this._chart.Data.Children[seriesIndex];
            double[,] values = ds.GetValues();
            if (values == null)
            {
                return -1;
            }
            Point point = this.PointToData(ds, pt);
            int num = 0;
            double y = point.Y;
            if (option == MeasureOption.X)
            {
                num = 1;
                y = point.X;
            }
            else if (option == MeasureOption.XY)
            {
                num = 2;
            }
            int num3 = -1;
            int length = values.GetLength(0);
            Axis axisX = this.GetAxisX(ds);
            Axis axisY = this.GetAxisY(ds);
            double actualMin = axisX.ActualMin;
            double actualMax = axisX.ActualMax;
            double num7 = axisY.ActualMin;
            double num8 = axisY.ActualMax;
            if (values != null)
            {
                int num9 = values.GetLength(1);
                for (int i = 0; i < num9; i++)
                {
                    double d = (length > 1) ? values[1, i] : ((double)i);
                    double num12 = values[0, i];
                    if (((d >= actualMin) && (d <= actualMax)) && ((num12 >= num7) && (num12 <= num8)))
                    {
                        if (num == 2)
                        {
                            if (!double.IsNaN(d) && !double.IsNaN(num12))
                            {
                                Point point2 = this.PointFromData(ds, new Point(d, num12));
                                double num13 = point2.X - pt.X;
                                double num14 = point2.Y - pt.Y;
                                double num15 = Math.Sqrt((num13 * num13) + (num14 * num14));
                                if (num15 < distance)
                                {
                                    distance = num15;
                                    num3 = i;
                                }
                            }
                        }
                        else
                        {
                            double num16 = (num == 1) ? d : num12;
                            if (!double.IsNaN(num16))
                            {
                                double num17 = Math.Abs((double)(num16 - y));
                                if (num17 < distance)
                                {
                                    distance = num17;
                                    num3 = i;
                                }
                            }
                        }
                    }
                }
            }
            if (distance != double.MaxValue)
            {
                if (option == MeasureOption.X)
                {
                    distance = this._vp2.ConvertX(distance) - this._vp2.ConvertX(0.0);
                    return num3;
                }
                if (option == MeasureOption.Y)
                {
                    distance = this._vp2.ConvertY(distance) - this._vp2.ConvertY(0.0);
                }
            }
            return num3;
        }

        public Point DataIndexToPoint(int seriesIndex, int pointIndex)
        {
            Point pt = new Point(double.NaN, double.NaN);
            int count = this._chart.Data.Children.Count;
            DataSeries ds = this._chart.Data.Children[seriesIndex];
            double[,] values = ds.GetValues();
            if (values == null)
            {
                return pt;
            }
            int length = values.GetLength(0);
            int num2 = values.GetLength(1);
            if ((pointIndex < 0) || (pointIndex >= num2))
            {
                throw new ArgumentOutOfRangeException("pointIndex");
            }
            pt.Y = values[0, pointIndex];
            pt.X = (length > 1) ? values[1, pointIndex] : ((double)pointIndex);
            return this.PointFromData(ds, pt);
        }

        internal Axis GetAxisX(DataSeries ds)
        {
            if (!string.IsNullOrEmpty(ds.AxisX))
            {
                Axis axis = this.Axes[ds.AxisX];
                if (axis != null)
                {
                    return axis;
                }
            }
            return this.AxisX;
        }

        internal Axis GetAxisY(DataSeries ds)
        {
            if (!string.IsNullOrEmpty(ds.AxisY))
            {
                Axis axis = this.Axes[ds.AxisY];
                if (axis != null)
                {
                    return axis;
                }
            }
            return this.AxisY;
        }

        public Point GetDataPoint(UIElement visual)
        {
            return this._childPoints[visual];
        }

        public Point GetPosition(MouseEventArgs e)
        {
            if (this.ViewElement != null)
            {
                return e.GetPosition(this.ViewElement);
            }
            return new Point(double.NaN, double.NaN);
        }

        private static void OnInvertedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ChartView view = (ChartView)obj;
            Chart chart = view._chart;
            if (chart != null)
            {
                chart.ApplyChartType(chart.ChartType);
            }
        }

        private static void OnPlotBackgroundChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ChartView view = obj as ChartView;
            if (view.PlotShape != null)
            {
                view.PlotShape.Fill = e.NewValue as Brush;
                if (view.PlotShape.Fill == null)
                {
                    view.PlotShape.Fill = view._transparentBrush;
                }
            }
        }

        public Point PointFromData(Point pt)
        {
            return new Point(this._vp2.ConvertX(pt.X), this._vp2.ConvertY(pt.Y));
        }

        private Point PointFromData(DataSeries ds, Point pt)
        {
            return this.PointFromData(ds.AxisX, ds.AxisY, pt);
        }

        internal Point PointFromData(string axisx, string axisy, Point pt)
        {
            Point point = pt;
            if (!string.IsNullOrEmpty(axisx))
            {
                Axis axis = this.Axes[axisx];
                if (axis != null)
                {
                    pt = axis.PointFromData(pt);
                }
                if (!string.IsNullOrEmpty(axisy))
                {
                    double x = pt.X;
                    Axis axis2 = this.Axes[axisy];
                    if (axis2 != null)
                    {
                        pt = axis2.PointFromData(point);
                    }
                    pt.X = x;
                }
                return pt;
            }
            if (!string.IsNullOrEmpty(axisy))
            {
                Axis axis3 = this.Axes[axisy];
                if (axis3 != null)
                {
                    pt = axis3.PointFromData(pt);
                    return pt;
                }
                pt = this.PointFromData(pt);
                return pt;
            }
            pt = this.PointFromData(pt);
            return pt;
        }

        public Point PointToData(Point pt)
        {
            return new Point(this._vp2.ConvertBackX(pt.X), this._vp2.ConvertBackY(pt.Y));
        }

        private Point PointToData(DataSeries ds, Point pt)
        {
            return this.PointToData(ds.AxisX, ds.AxisY, pt);
        }

        internal Point PointToData(string axisx, string axisy, Point pt)
        {
            Point point = pt;
            if (!string.IsNullOrEmpty(axisx))
            {
                Axis axis = this.Axes[axisx];
                if (axis != null)
                {
                    pt = axis.PointToData(pt);
                }
                if (!string.IsNullOrEmpty(axisy))
                {
                    double x = pt.X;
                    Axis axis2 = this.Axes[axisy];
                    if (axis2 != null)
                    {
                        pt = axis2.PointToData(point);
                    }
                    pt.X = x;
                }
                return pt;
            }
            if (!string.IsNullOrEmpty(axisy))
            {
                Axis axis3 = this.Axes[axisy];
                if (axis3 != null)
                {
                    pt = axis3.PointToData(pt);
                    return pt;
                }
                pt = this.PointToData(pt);
                return pt;
            }
            pt = this.PointToData(pt);
            return pt;
        }

        internal void Rebuild(double w, double h)
        {
            if ((w > 0.0) && (h > 0.0))
            {
                this._vp2.UpdateLayout(new Size(w, h));
            }
        }

        internal void RemoveAxis(Axis ax)
        {
            ax.PropertyChanged -= new PropertyChangedEventHandler(this._axis_Changed);
        }

        private void ReplaceAxis(Axis oldAxis, Axis newAxis)
        {
            if ((oldAxis != null) && (newAxis != null))
            {
                int index = this.Axes.IndexOf(oldAxis);
                if (index >= 0)
                {
                    this.Axes[index] = newAxis;
                }
            }
        }

        internal void ResetInternal()
        {
            this.Children.Clear();
            if (this.AxisX != null)
            {
                this.AxisX.Reset();
            }
            if (this.AxisY != null)
            {
                this.AxisY.Reset();
            }
            this.Axes.Clear();
        }

        public void SetDataPoint(UIElement visual, Point point)
        {
            this._childPoints[visual] = point;
        }

        internal void UpdateChildPositions()
        {
            foreach (UIElement element in this.Children)
            {
                if (this._childPoints.ContainsKey(element))
                {
                    Point point = this.PointFromData(this._childPoints[element]);
                    element.SetValue(Canvas.LeftProperty, point.X);
                    element.SetValue(Canvas.TopProperty, point.Y);
                }
                else
                {
                    element.SetValue(Canvas.LeftProperty, 0.0);
                    element.SetValue(Canvas.TopProperty, 0.0);
                }
            }
        }

        internal PlotArea UpdateMainPlotArea(bool hasPlotAreas)
        {
            if (!hasPlotAreas)
            {
                PlotArea area = null;
                int count = this.PlotAreas.Count;
                for (int i = 0; i < count; i++)
                {
                    if ((this.PlotAreas[i].Column == this.AxisX.PlotAreaIndex) && (this.PlotAreas[i].Row == this.AxisY.PlotAreaIndex))
                    {
                        area = this.PlotAreas[i];
                        break;
                    }
                }
                if (area != null)
                {
                    return area;
                }
            }
            return null;
        }

        // Properties
        public AxisCollection Axes
        {
            get
            {
                return this._axes;
            }
        }

        public Axis AxisX
        {
            get
            {
                return this._ax;
            }
            set
            {
                if ((this._ax != value) && (value != null))
                {
                    this.ReplaceAxis(this._ax, value);
                    this._ax = value;
                    this._ax.SetFixedType(AxisType.X);
                    this._ax.PropertyChanged += new PropertyChangedEventHandler(this._axis_Changed);
                    if (this._vp2 != null)
                    {
                        this._vp2.SetAxisX(this._ax);
                    }
                }
            }
        }

        public Axis AxisY
        {
            get
            {
                return this._ay;
            }
            set
            {
                if ((this._ay != value) && (value != null))
                {
                    this.ReplaceAxis(this._ay, value);
                    this._ay = value;
                    this._ay.SetFixedType(AxisType.Y);
                    this._ay.PropertyChanged += new PropertyChangedEventHandler(this._axis_Changed);
                    if (this._vp2 != null)
                    {
                        this._vp2.SetAxisY(this._ay);
                    }
                }
            }
        }

        internal Chart Chart
        {
            get
            {
                return this._chart;
            }
            set
            {
                if (this._chart != value)
                {
                    this._chart = value;
                    if (this._layers != null)
                    {
                        foreach (IChartLayer layer in this._layers)
                        {
                            layer.Chart = this._chart;
                        }
                    }
                    if (this._axes != null)
                    {
                        foreach (Axis axis in this._axes)
                        {
                            axis.Chart = this._chart;
                        }
                    }
                }
            }
        }

        public System.Collections.ObjectModel.ObservableCollection<UIElement> Children
        {
            get
            {
                if (this._children == null)
                {
                    this._children = new System.Collections.ObjectModel.ObservableCollection<UIElement>();
                    this._children.CollectionChanged += new NotifyCollectionChangedEventHandler(this._children_CollectionChanged);
                }
                return this._children;
            }
        }

        internal bool HasPlotAreas
        {
            get
            {
                int count = this.Axes.Count;
                for (int i = 0; i < count; i++)
                {
                    if (this.Axes[i].PlotAreaIndex > 0)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        public bool Inverted
        {
            get
            {
                return (bool)base.GetValue(InvertedProperty);
            }
            set
            {
                base.SetValue(InvertedProperty, value);
            }
        }

        public System.Collections.ObjectModel.ObservableCollection<IChartLayer> Layers
        {
            get
            {
                return this._layers;
            }
        }

        public System.Collections.ObjectModel.ObservableCollection<PlotAreaColumnDefinition> PlotAreaColumnDefinitions
        {
            get
            {
                return this._areas.ColumnDefinitions;
            }
        }

        public System.Collections.ObjectModel.ObservableCollection<PlotAreaRowDefinition> PlotAreaRowDefinitions
        {
            get
            {
                return this._areas.RowDefinitions;
            }
        }

        public PlotAreaCollection PlotAreas
        {
            get
            {
                return this._areas;
            }
        }

        public Brush PlotBackground
        {
            get
            {
                return (Brush)base.GetValue(PlotBackgroundProperty);
            }
            set
            {
                base.SetValue(PlotBackgroundProperty, value);
            }
        }

        public Rect PlotRect
        {
            get
            {
                return this._vp2.PlotRect;
            }
        }

        public Shape PlotShape
        {
            get
            {
                return this._plotShape;
            }
        }

        internal IRenderer Renderer
        {
            get
            {
                return this._renderer;
            }
            set
            {
                if (this._renderer != value)
                {
                    this._renderer = value;
                    this._vp2.Reset();
                }
            }
        }

        internal FrameworkElement ViewElement
        {
            get
            {
                return this._vp2;
            }
        }
    }
}
